package authentication

import (
	"encoding/json"
	"errors"
	log "github.com/cihub/seelog"
	"net"
	"strconv"
	"strings"
	"time"
	"tunnel/common/config"
	"tunnel/utils/timer"
)

var (
	ErrAuthTimeout   = errors.New("timeout")
	ErrAuthConnect   = errors.New("connect to authentication server failed")
	ErrAuthBadPacket = errors.New("invalid authentication packet")
	ErrAuthFailed    = errors.New("authentication failed")
)

//
// AuthClientV2
// @Description:
//
type AuthClientV2 struct {
	Config  config.Auth
	conn    *net.TCPConn
	uuid    string
	sig     chan error
	handler AuthClientHandler
	login   bool
}

//
// NewClientV2
// @Description:
// @param cfg
// @return client
// @return err
//
func NewClientV2(cfg config.Auth, handler AuthClientHandler) (client *AuthClientV2, err error) {
	address := strings.Join([]string{cfg.Address, strconv.Itoa(cfg.Port)}, ":")
	log.Info("connect to authentication server : ", address)
	addr, _ := net.ResolveTCPAddr("tcp", address)
	conn, err := net.DialTCP("tcp", nil, addr)
	if err != nil {
		return nil, ErrAuthConnect
	}
	_ = conn.SetKeepAlive(true)
	c := &AuthClientV2{Config: cfg, uuid: UUID(), conn: conn, sig: make(chan error, 1), handler: handler, login: false}
	go c.handle()
	return c, nil
}

//
// handle
// @Description:
// @receiver c
//
func (c *AuthClientV2) handle() {
	buffer := make([]byte, MaxAuthPacketSize)
	for {
		n, err := c.conn.Read(buffer)
		if err != nil || n == 0 {
			c.handler.OnDisconnect()
			return
		}
		packet := NewTransportPacket()
		err = packet.Decode(buffer[:n])
		if err != nil {
			break
		}
		if packet.UUID == c.uuid && packet.Type != PacketTypeUnknown {
			switch packet.Type {
			case PacketTypeLogin, PacketTypeLogout:
				reply := &AuthReply{}
				_ = json.Unmarshal(packet.Payload, reply)
				if reply.Ok {
					c.sig <- nil
				} else {
					c.sig <- errors.New(reply.Error)
				}
			case PacketTypeMsg:
				c.handler.OnMessage(string(packet.Payload))
			case PacketTypeReport:
				c.handler.OnReport(packet.Payload)
			}
		}
	}
}

//
// Logout
// @Description:
// @receiver c
// @return err
//
func (c *AuthClientV2) Logout() error {
	if !c.login {
		return nil
	}
	configBytes, err := json.Marshal(config.Current)
	if err != nil {
		return ErrAuthBadPacket
	}
	packet := TransportPacket{
		Type:    PacketTypeLogout,
		UUID:    c.uuid,
		Payload: configBytes,
	}
	err = packet.Send(c.conn)
	if err != nil {
		return ErrAuthBadPacket
	}
	if err := timer.TimeoutTask(func() error {
		return <-c.sig
	}, time.Second*5); err != nil {
		log.Info("failed to logout : ", err.Error())
		c.handler.OnLogout(err)
		c.handler.OnDisconnect()
		return ErrAuthFailed
	}
	log.Info("logout success")
	c.handler.OnLogout(nil)
	c.handler.OnDisconnect()
	return nil
}

//
// Login
// @Description:
// @receiver c
// @return err
//
func (c *AuthClientV2) Login() (err error) {
	configBytes, err := json.Marshal(config.Current)
	if err != nil {
		return ErrAuthBadPacket
	}
	packet := TransportPacket{
		Type:    PacketTypeLogin,
		UUID:    c.uuid,
		Payload: configBytes,
	}
	err = packet.Send(c.conn)
	if err != nil {
		return ErrAuthBadPacket
	}
	if err := timer.TimeoutTask(func() error {
		return <-c.sig
	}, time.Second*30); err != nil {
		log.Info("failed to login : ", err.Error())
		c.handler.OnLogin(err)
		if err == timer.Timeout {
			return ErrAuthTimeout
		}
		return ErrAuthFailed
	}
	log.Info("login success")
	c.handler.OnLogin(nil)
	c.login = true
	return nil
}
